package httproxy

import (
	"errors"
	"fmt"
	"github.com/jinzhu/copier"
	"github.com/sirupsen/logrus"
	"http_proxy_v2/internal/balance"
	"http_proxy_v2/internal/healthchecker"
	"io"
	"net/http"
	"time"
)

func (info *ConnectInfo) Incr(isErr bool, index int) (err error) {
	info.mu.Lock()
	defer info.mu.Unlock()
	if isErr {
		info.ErrData[index] += 1
	} else {
		info.Data[index] += 1
	}
	return
}

func NewUpClient(upStream UpstreamServer) UpClient {
	var upClient UpClient
	copier.Copy(&upClient, &upStream)
	upClient.InitClient()
	return upClient
}

func (uc UpClient) UID() int {
	return uc.ID
}

func (uc *UpClient) InitClient() {
	uc.Client = http.Client{
		Transport: &http.Transport{
			MaxConnsPerHost: uc.MaxConn,
		},
		Timeout: time.Duration(uc.TimeOut) * time.Second,
	}
}

func (uc UpClient) Weight() int {
	return uc.UpstreamServer.Weight
}

func (s Proxy)GetServers()[]healthchecker.Server{
	servers := []healthchecker.Server{}
	for group,location := range s.Locations{
		for _,v := range location.Up.UpStreamInfo{
			servers = append(servers,healthchecker.Server{NewWork: v.NetWork,Addr: v.Addr,OriginalData: v,GroupIndex: group})
		}
	}
	return servers
}


func NewProxy(proxy Proxy) Proxy {
	for k, location := range proxy.Locations {
		proxy.Locations[k].Up.balance, _ = balance.NewService(location.Up.Name)
		proxy.Locations[k].ConnInfo.Data = make(map[int]uint64)
		proxy.Locations[k].ConnInfo.ErrData = make(map[int]uint64)
		for _, upstream := range location.Up.UpStreamInfo {
			upClient := NewUpClient(upstream.UpstreamServer)
			proxy.Locations[k].Up.UpStreamInfo = append(proxy.Locations[k].Up.UpStreamInfo, upClient)
			proxy.Locations[k].Up.balance.AddInstance(upClient)
		}
		proxy.Locations[k].Up.balance.GenerateStaticInfo()
		le := len(proxy.Locations[k].Up.UpStreamInfo) / 2
		proxy.Locations[k].Up.UpStreamInfo = proxy.Locations[k].Up.UpStreamInfo[le:]
	}
	return proxy
}

// ServeRequest 提供服务
func (proxy *Proxy) ServeRequest(w http.ResponseWriter, r *http.Request) () {
	if r.URL.Path == "/favicon.ico" {
		return
	}
	var upClient UpClient
	gIndex, err := getGroupIndex(proxy, r)
	if err != nil {
		logrus.Error("负载均衡策略出错:", err)
		w.WriteHeader(503)
		w.Write([]byte(err.Error()))
		return
	}
	//负载均衡
	index, err := proxy.Locations[gIndex].Up.balance.DoBalance(r)
	if err != nil {
		logrus.Error("负载均衡策略出错:", err)
		w.WriteHeader(503)
		w.Write([]byte(err.Error()))
		return
	} else {
		upClient = proxy.Locations[gIndex].Up.UpStreamInfo[index]
		/*for _, upstream := range  proxy.Locations[gIndex].Up.UpStreamInfo{
			if upstream.ID-1 == index {
				upClient = upstream
				break
			}
		}*/
	}
	path := fmt.Sprintf("%s://%s%s", upClient.UpstreamServer.NetWork, upClient.UpstreamServer.Addr, r.RequestURI)
	if r.URL.RawQuery != "" {
		path = path + "&" + r.URL.RawQuery
	}
	req, _ := http.NewRequest(r.Method, path, r.Body)
	response, err := upClient.Client.Do(req)
	if err != nil {
		w.WriteHeader(500)
		w.Write([]byte(err.Error()))
		go func() { proxy.Locations[gIndex].ConnInfo.Incr(true, index) }()
	} else {
		copyHeader(w.Header(), response.Header)
		io.Copy(w, response.Body)
		go func() { proxy.Locations[gIndex].ConnInfo.Incr(false, index) }()
	}
	return
}

func copyHeader(dst, src http.Header) {
	for k, vv := range src {
		for _, v := range vv {
			dst.Add(k, v)
		}
	}
}

func getGroupIndex(proxy *Proxy, r *http.Request) (int, error) {
	path := r.URL.Path
	//rawQuery := r.URL.RawQuery
	index := -1
	for k, location := range proxy.Locations {
		if path == location.Value {
			index = k
			break
		}
	}
	if index < 0 {
		return index, errors.New("upstream server the group existent index!")
	}
	return index, nil
}
